package moodish.test;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Random;

import moodish.comm.ClientSideMessage;
import moodish.comm.ServerSideMessage;
import moodish.dummy.comm.CSM;
import moodish.dummy.comm.SSM;
import moodish.dummy.comm.client.ClientCommDummy;
import moodish.dummy.comm.server.ServerCommDummy;

import org.junit.Assert;
import org.junit.Test;

public class ClietServerCommInteractionTestCase {
	public final static String DEF_HOST = "localhost";
	public final static String DEF_NICKNAME_CLIENT_1 = "My Nickname 1";
	public final static String DEF_NICKNAME_CLIENT_2 = "My Nickname 2";
	private Random random = new Random();

	private ServerCommDummy server;
	private ClientCommDummy client1;
	private ClientCommDummy client2;
	
	
	/**
	 * Tests if the Server can sheutdown.
	 */
	@Test
	public void ServerShutdownTest(){
		establishOneToManyConnection();

		try {
			server.shutdown();
			sleep();
			Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
			Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_2));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	
	/**
	 * Tests if the Server can disconnect from a Client.
	 */
	@Test
	public void ServerDisconnectClientTest(){
		establishOneToManyConnection();
		
		Assert.assertTrue(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
		server.disconnectClient(DEF_NICKNAME_CLIENT_1);
		Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
	}
	
	/**
	 * Tests if the Server to Client Connect message is working.
	 */
	@Test
	public void ServerToClientConnectTest(){
		establishOneToManyConnection();
		
		server.sendClientConnected(DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2);
		String expectedMessage = new CSM(ClientSideMessage.Type.CONNECTED, DEF_NICKNAME_CLIENT_2, null).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	/**
	 * Tests if the Server to Client Disconnect message is working.
	 */
	@Test
	public void ServerToClientDisconnectTest(){
		establishOneToManyConnection();
		
		server.sendClientDisconnected(DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2);
		String expectedMessage = new CSM(ClientSideMessage.Type.DISCONNECTED, DEF_NICKNAME_CLIENT_2, null).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	/**
	 * Tests if the Server to Client Friendship message is working.
	 */
	@Test
	public void ServerToClientFriendshipTest(){
		establishOneToManyConnection();
		
		server.sendNewFriendship(DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2);
		sleep();
		Assert.assertTrue(client1.hasNextMessage());
		String expectedMessage = new CSM(ClientSideMessage.Type.FRIENDSHIP, ClientSideMessage.SERVER, DEF_NICKNAME_CLIENT_2).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	/**
	 * Tests if the Server to Client Unfriendship message is working.
	 */
	@Test
	public void ServerToClientUnfriendshipTest(){
		establishOneToManyConnection();
		
		server.sendNewUnfriendship(DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2);
		String expectedMessage = new CSM(ClientSideMessage.Type.UNFRIENDSHIP, ClientSideMessage.SERVER, DEF_NICKNAME_CLIENT_2).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	/**
	 * Tests if the Server to Client Moodish message is working.
	 */
	@Test
	public void ServerToClientMoodishMessageTest(){
		establishOneToManyConnection();
		
		String message = "Moodish Message";
		server.sendMoodishMessage(DEF_NICKNAME_CLIENT_2, DEF_NICKNAME_CLIENT_1, message);
		String expectedMessage = new CSM(ClientSideMessage.Type.MOODISH_MESSAGE, DEF_NICKNAME_CLIENT_2, message).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	
	/**
	 * Tests if the Server to Client Error message is working.
	 */
	@Test
	public void ServerToClientErrorTest(){
		establishOneToManyConnection();
		
		String message = "Error Message";
		server.sendError(DEF_NICKNAME_CLIENT_1, message);
		String expectedMessage = new CSM(ClientSideMessage.Type.ERROR, ClientSideMessage.SERVER, message).toString();
		String recivedMessage = client1.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(client1.hasNextMessage());
	}
	
	
	/**
	 * Tests if the Client to Server Disconnect message is working.
	 */
	@Test
	public void ClientToServerDisconnectTest(){
		establishOneToManyConnection();
		
		client1.disconnect();
		String expectedMessage = new SSM(ServerSideMessage.Type.CLIENT_DISCONNECTED, DEF_NICKNAME_CLIENT_1, ClientCommDummy.REQUEST).toString();
		String recivedMessage = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
		Assert.assertFalse(server.hasNextMessage());
	}
	
	
	/**
	 * Tests if the Client to Server Friendship message is working.
	 */
	@Test
	public void ClientToServerFriendshipTest(){
		establishOneToManyConnection();
		
		client1.friendship(DEF_NICKNAME_CLIENT_2);
		String expectedMessage = new SSM(ServerSideMessage.Type.FRIENDSHIP, DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2).toString();
		String recivedMessage = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(server.hasNextMessage());
	}
	
	/**
	 * Tests if the Client to Server Unfriendship message is working.
	 */
	@Test
	public void ClientToServerUnfriendshipTest(){
		establishOneToManyConnection();
		
		client1.unfriendship(DEF_NICKNAME_CLIENT_2);
		String expectedMessage = new SSM(ServerSideMessage.Type.UNFREINDSHIP, DEF_NICKNAME_CLIENT_1, DEF_NICKNAME_CLIENT_2).toString();
		String recivedMessage = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(server.hasNextMessage());
	}
	
	/**
	 * Tests if the Client to Server Moodish message is working.
	 */
	@Test
	public void ClientToServerMoodishMessageTest(){
		establishOneToManyConnection();
		
		String message = "Moodish Message";
		client1.sendMoodishMessage(message);
		String expectedMessage = new SSM(ServerSideMessage.Type.MOODISH_MESSAGE, DEF_NICKNAME_CLIENT_1, message).toString();
		String recivedMessage = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage, recivedMessage);
		Assert.assertFalse(server.hasNextMessage());
	}
	
	/**
	 * Tests if a one to many connection is working.
	 */
	@Test
	public void oneToManyConnectTest(){
		establishOneToManyConnection();
	}
	
	private void establishOneToManyConnection(){
		int randomPort = generateRandomPort();

		// Create and start the Server
		server = new ServerCommDummy(randomPort);
		server.start();

		

		
		// Create and connect Client 1
		client1 = new ClientCommDummy(randomPort);
		
		// Verify if the client is connected
		Assert.assertFalse(client1.isConnected());
		Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
		
		try {
			client1.connect(DEF_HOST, DEF_NICKNAME_CLIENT_1);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		sleep();
		// Verify if the client is connected
		Assert.assertTrue(client1.isConnected());
		Assert.assertTrue(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
		
		// Verify if the server has any pending messages
		Assert.assertTrue(server.hasNextMessage());
		
		// Verify if the pending message if the expected Client Conected message
		String expectedMessage1 = new SSM(ServerSideMessage.Type.CLIENT_CONNECTED, DEF_NICKNAME_CLIENT_1, null).toString();
		String recivedMessage1 = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage1, recivedMessage1);
		
		// Verify if the server has any pending messages
		Assert.assertFalse(server.hasNextMessage());

		
		
		
		
		// Create and connect Client 2
		client2 = new ClientCommDummy(randomPort);
		
		// Verify if the client is connected
		Assert.assertFalse(client2.isConnected());
		Assert.assertFalse(server.clientIsConnected(DEF_NICKNAME_CLIENT_2));
		
		try {
			client2.connect(DEF_HOST, DEF_NICKNAME_CLIENT_2);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		sleep();
		
		// Verify if the client is connected
		Assert.assertTrue(client2.isConnected());
		Assert.assertTrue(server.clientIsConnected(DEF_NICKNAME_CLIENT_2));
		
		// Verify if the server has any pending messages
		Assert.assertTrue(server.hasNextMessage());
		
		// Verify if the pending message if the expected Client Conected message
		String expectedMessage2 = new SSM(ServerSideMessage.Type.CLIENT_CONNECTED, DEF_NICKNAME_CLIENT_2, null).toString();
		String recivedMessage2 = server.getNextMessage().toString();
		Assert.assertEquals(expectedMessage2, recivedMessage2);
		
		// Verify if the server has any pending messages
		Assert.assertFalse(server.hasNextMessage());
	}


	private int generateRandomPort() {
		return random.nextInt(ServerCommDummy.MAX_PORT_NUMBER) + ServerCommDummy.MIN_PORT_NUMBER;
	}

	private void sleep(){
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Tests if a one to one connection is working.
	 */
	@Test
	public void OneToOneConnectTest1(){
		ServerCommDummy server = new ServerCommDummy();
		server.start();

		ClientCommDummy client = new ClientCommDummy();
		try {
			client.connect(DEF_HOST, DEF_NICKNAME_CLIENT_1);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		sleep();
		Assert.assertTrue(client.isConnected());
		Assert.assertTrue(server.clientIsConnected(DEF_NICKNAME_CLIENT_1));
	}

	/**
	 * Tests what happens if an unknown hostname is given.
	 */
	@Test(expected = UnknownHostException.class)
	public void connectTest2() throws UnknownHostException{
		int randomPort = generateRandomPort();

		ServerCommDummy server = new ServerCommDummy(randomPort);
		server.start();

		ClientCommDummy client = new ClientCommDummy(randomPort);
		try {
			client.connect("local", DEF_NICKNAME_CLIENT_1);
		} catch (UnknownHostException e) {
			throw e;
		} catch (IOException e) {}
	}

	/**
	 * Tests what happens if an unknown IP address is given.
	 */
	@Test(expected = IOException.class)
	public void connectTest3() throws IOException{
		int randomPort = generateRandomPort();

		ServerCommDummy server = new ServerCommDummy(randomPort);
		server.start();

		ClientCommDummy client = new ClientCommDummy(randomPort);
		try {
			client.connect("125.0.0.1", DEF_NICKNAME_CLIENT_1);
		} catch (UnknownHostException e) {

		} catch (IOException e) {
			throw e;
		}
	}
}
